package com.sailing.xjpb.common;

import com.sailing.xjpb.common.exception.BusinessException;
import com.sailing.xjpb.common.exception.ConvertException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 身份证号码工具类
 *
 * @author YW
 * create 2021/4/15
 */
@Slf4j
public class IdNumberUtils {

    private IdNumberUtils() {

    }

    private static final int[] coefficient = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};

    private static final char[] lastCharArray = new char[]{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};

    public static final String GMSFHM_REGEX = "\\d{15}|\\d{17}[\\dxX]";

    /**
     * 性别为男
     */
    public static final String SEX_MAN = "1";
    /**
     * 性别为男
     */
    public static final String SEX_MAN_NAME = "男";
    /**
     * 性别为女
     */
    public static final String SEX_WOMAN = "2";
    /**
     * 性别为女
     */
    public static final String SEX_WOMAN_NAME = "女";
    /**
     * 未知性别
     */
    public static final String SEX_UNKNOWN = "9";
    /**
     * 性别为未知性别
     */
    public static final String SEX_UNKNOWN_NAME = "未知性别";

    /**
     * 定义常量15进行计算
     */
    public static final int LEN_15 = 15;

    /**
     * 定义常量18进行计算
     */
    public static final int LEN_18 = 18;

    /**
     * 验证身份证是否正确，不校验空值
     *
     * @param idNumber 待校验的身份证号
     * @return true 为空或是身份证号码 | false 不为空且不是身份证号码
     */
    public static boolean validateIgnoreNull(String idNumber) {
        return StringUtils.isEmpty(idNumber) || validate(idNumber);
    }

    /**
     * @param idNumber 待校验的身份证号
     *                 Description: 验证身份证是否正确
     * @return true 身份证号码 | false 不是身份证号码
     */
    public static boolean validate(String idNumber) {
        try {
            if (StringUtils.isBlank(idNumber)) {
                return false;
            }
            if (idNumber.length() == LEN_15) {
                return IdNumber15Utils.isIDCard(idNumber);
            }
            //身份证号位数校验
            if (!idNumber.matches(GMSFHM_REGEX)) {
                return false;
            }
            //18位身份证号合法性校验
            return idNumber.length() == LEN_18 && check(idNumber);
        } catch (Exception e) {
            log.info("验证身份证失败！", e);
            return false;
        }
    }

    /**
     * 根据身份证号获取出生日期
     *
     * @param idNumber 身份证号
     * @return 出生日期yyyy-MM-dd
     */
    public static String getBirthdayStr(String idNumber) {
        try {
            if (!validate(idNumber)) {
                return null;
            }
            //15位身份证号
            if (StringUtils.length(idNumber) == LEN_15) {
                return IdNumber15Utils.getCsrq(idNumber);
            }
            // 身份证上的年份
            String year = idNumber.substring(6, 10);
            // 身份证上的月份
            String month = idNumber.substring(10, 12);
            //身份证上的日期
            String day = idNumber.substring(12, 14);
            return year + "-" + month + "-" + day;
        } catch (Exception e) {
            throw BusinessException.businessError("根据身份证号码获取出生日期失败！", e);
        }
    }

    /**
     * 根据身份证号获取出生日期
     *
     * @param idNumber 身份证号
     * @return java.lang.String 出生日期yyyy-MM-dd
     * @author 王康
     */
    public static Date getBirthday(String idNumber) {
        String birthdayStr = getBirthdayStr(idNumber);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            return sdf.parse(birthdayStr);
        } catch (ParseException e) {
            throw new ConvertException("获取身份证号码的出生日期出错！");
        }
    }

    /**
     * 根据身份证号计算性别
     *
     * @param idNumber 身份证号
     * @return 性别
     */
    public static String getSex(String idNumber) {
        if (validate(idNumber)) {
            if (idNumber.length() == LEN_15) {
                return IdNumber15Utils.getSex(idNumber);
            }
            String sexFlag = idNumber.substring(16, 17);
            String model = String.valueOf(Integer.parseInt(sexFlag) % 2);
            return SEX_MAN.equals(model) ? SEX_MAN : SEX_WOMAN;
        }
        return SEX_UNKNOWN;
    }

    /**
     * 根据身份证号计算性别
     *
     * @param idNumber 身份证号
     * @return 性别名称
     */
    public static String getSexName(String idNumber) {
        String sex = getSex(idNumber);
        if (SEX_MAN.equals(sex)) {
            return SEX_MAN_NAME;
        } else if (SEX_WOMAN.equals(sex)) {
            return SEX_WOMAN_NAME;
        } else {
            return SEX_UNKNOWN_NAME;
        }
    }

    public static char[] idCardChars(String idCardNum) {
        return idCardNum.toCharArray();
    }

    public static String getLastChar(String idCardNum) {
        return idCardNum.substring(idCardNum.length() - 1);
    }

    /**
     * 基本验证原理是通过第十八位上的字符座位校验码进行验证的：
     * 1.将前面的身份证号码17位数分别乘以不同的系数： 从第一位到第十七位的系数分别为：7. 9 .10 .5. 8. 4. 2. 1. 6. 3. 7.9. 10. 5. 8. 4. 2.
     * 2.将这17位数字和系数相乘的结果相加。
     * 3.用加出来和除以11，看余数是多少？
     * 4余数只可能有0 、1、2、 3、 4、 5、 6、 7、 8、9、 10这11个数字。 其分别对应的最后一位身份证的号码为1 .0. X. 9. 8. 7. 6. 5. 4. 3. 2.
     * 5.通过上面得知如果余数是2，就会在身份证的第18位数字上出现罗马数字的x。 如果余数是10，身份证的最后一位号码就是2。 　倒数第二位是用来表示性别的
     * 例如：某男性的身份证号码是34052419800101001X。我们要看看这个身份证是不是合法的身份证。 　首先：我们得出，前17位的乘积和是189
     * 然后：用189除以11得出的结果是17 + 2/11，也就是说余数是2。 　最后：通过对应规则就可以知道余数2对应的数字是x。
     * 所以，这是一个合格的身份证号码。
     *
     * @param idCardNum 身份证号
     * @return boolean
     * @author 王康
     */
    public static boolean check(String idCardNum) {
        return getLastChar(idCardNum).equals(String.valueOf(getValidateCode(idCardNum)));
    }

    public static char getValidateCode(String idCardNum) {
        char[] idNumChars = idCardChars(idCardNum);
        int sum = 0;
        for (int i = 0; i < idNumChars.length - 1; i++) {
            int ai = Integer.parseInt(String.valueOf(idNumChars[i]));
            int wi = getWeightingFactor(i);

            if (wi != -1) {
                sum += ai * wi;
            }
        }
        return lastCharArray[sum % 11];
    }

    public static int getWeightingFactor(int index) {
        if (index >= coefficient.length) {
            return -1;
        }
        return coefficient[index];
    }

}
